iT邦幫忙

2023 iThome 鐵人賽

DAY 6
2
WordPress

從 0 到 100:WordPress 開發者的實戰手冊系列 第 6

Day 6 - WordPress 高擴充性的機制 - 鉤點 (Hook) 及過濾器 (Filter)

  • 分享至 

  • xImage
  •  

WordPress 使用了一種軟體設計模式,在程式的生命週期各個重要階段埋入一種稱為鉤點 (hook) 及過濾器 (filter) 的節點,提供讓外掛及佈景主題插入其所要提供的功能。

很久以前,筆者剛初學 PHP 程式設計時,對於 WordPress 開發外掛很有興趣,那時候看到這些鉤點和過濾器的時候,被搞的一頭霧水,有看沒有懂,那時候就沒繼續摸索 WordPress 的程式開發了。後來經過幾年後,當自己程式設計的功力有些成長後,再回頭看這些東西,猛一看就明白了。現在想想,經驗的累積會幫助快速理解及學習新事物。如果你是初學者,覺得看不懂,千萬不要灰心。這篇文章,會盡量以淺顯易懂的方式,帶大家認識它們的作用,以及如何使用。

文章頁主要 Hook 生命週期

首先,先來看看當 WordPress 載入它的核心檔案後,陸續載入的類別、函式、甚至是佈景主題,都會埋有這些節點。

文章頁主要 Hook 生命週期
圖:WordPress 文章頁 Hook 生命週期

上圖為,當一個使用者拜訪你的網站時,閱讀了文章頁,會觸發的鉤點。標為紅色的節點為常用的重要節點。標為綠色字為「動態 slug」表示該鉤點的名稱不是寫死的,而是有一個變數結合成的名稱。

我們先來看看 template_redirect 這個節點在那裡被觸發。

template_redirect
圖:template-loader.php 檔案內容開頭

在 WordPress 進行初始化的時候,依續載入各個核心檔案,輪到載入 template-loader.php 這個檔案的時候,檔案開頭就埋入了 tempalte_redirect 這個鉤點。

在進行載入佈景主題之前,可以用這個鉤點做一些事,以 WordPress 自己拿這個鉤點來做的事,當作例子。

default-filter.php 檔案內容
圖:default-filter.php 檔案內容片段

default-filter.php 的第 454 行,可以看到:

add_action( 'template_redirect', 'wp_old_slug_redirect' );

add_action 這個函式是用來將我們指定的函式名稱,註冊到 template_redirect 這個鈎點裡,當程式執行到這個鉤點時,就會執行該註冊的函式。

以這個例子來說,WordPress 註冊了 wp_old_slug_redirect 這個函式到這個 tempalte_redirect 中,用途是用來把舊的網址轉到新的網址。而這個函式是定義在 query.php 這個檔案中,是更早之前就載入了,因此執行到這個階段時,找的到這個函式,並執行它。

主要 Hook 列表

這邊的連結是直接連往官方文件,可查詢用法。

hook 說明
muplugins_loaded 在必須載入和多站點網路已啟用的外掛都載入完成之後觸發。
registered_taxonomy 在分類及標籤等功能註冊完成後觸發。
registered_post_type 在文章類型註冊完成後觸發。
plugins_loaded 在已啟用的外掛載入之後,可插拔的函式載入之前觸發。
sanitize_comment_cookies 在迴響功能的 Cookies 被過濾時觸發。
setup_theme 在載入佈景主題之前觸發。
load_textdomain 在載入 MO 格式的翻譯檔之前觸發。
after_setup_theme 在每次頁面讀取時,是在佈景主題初始化之後被觸發。通常用於為佈景主題執行基本的設定、註冊和初始化操作。觸發時是在活動主題的 functions.php 文件載入後。
auth_cookie_malformed 身份驗證的 Cookie 格式不正確,則會觸發。
auth_cookie_valid 身份驗證的 Cookie 已被驗證時觸發。
set_current_user 當前用戶被設定之後觸發。
init WordPress 載入完成,但在發送任何頭部資訊之前觸發。
widgets_init 所有預設的 WordPress 小工具都已註冊後觸發。
register_sidebar 當一個側邊欄被註冊後觸發。
wp_register_sidebar_widget 對於每一個已註冊的小工具觸發一次。
wp_default_scripts WP_Scripts 實例初始化時觸發。
wp_default_styles WP_Styles 實例初始化時觸發。
admin_bar_init WP_Admin_Bar 實例初始化時觸發。
add_admin_bar_menus 在選單被添加到選單欄之後觸發。
wp_loaded WP、所有插件和主題都完全加載和實例化後觸發。
parse_request 當前請求的所有查詢變變都已被解析時觸發。
parse_query 在主要查詢變數被解析之後觸發。
pre_get_posts 在查詢變數的物件被建立之後,但在實際執行查詢之前觸發。
send_headers 當已發送請求的 HTTP 頭部資訊(用於快取、內容類型等)時,觸發一次。
posts_selection 在查詢之前執行,並傳遞一個包含已組裝查詢的字符串。
wp 當 WordPress 環境已被設定,WP 物件實例化完成後,觸發一次。
template_redirect 在決定載入那一個佈景主題之前觸發。
get_header 載入頁面的頭部版型檔案之前觸發。
wp_enqueue_scripts 當 JavaScript 程式和 CSS 樣式表檔案被加入隊列時觸發。
wp_head 用在前端的 head 標籤中輸出 JS、CSS 或其它資料。
wp_print_styles 在 $handles 隊列中的樣式被輸出之前觸發。
wp_print_scripts 在 $handles 隊列中的程式被輸出之前觸發。
get_search_form 過濾搜尋表單的 HTML 輸出。
loop_start 當輸出文章的迴圈開始時觸發。
the_post 文章物件被設定完成時觸發。
get_template_part_content 內容的版型檔案被載入之前觸發。
loop_end 輸出文章的迴圈結束後觸發。
get_sidebar 在側邊欄版型檔案被載入之前觸發。
dynamic_sidebar 在呼叫小工具的顯示用的回呼函式之前觸發。
pre_get_comments 在取得迴響的資料之前觸發。
wp_meta 在側邊欄中顯示輸出的內容之前觸發。
get_footer 在網頁的底部版型檔案被載入之前觸發。
wp_footer 在前端的 body 標籤結束之前輸出 JavaScript 程式或資料。
wp_print_footer_scripts 網頁的底部輸出 JavaScript 程式本文。
admin_bar_menu 讀取管理欄所需要的資料時觸發。
wp_before_admin_bar_render 在管理欄渲染之前。
wp_after_admin_bar_render 在管理欄渲染之後。
shutdown 在 PHP 執行即將結束之前。

函式用法

add_action

add_action( $hook_name, $function, $priority = 10, $accepted_args = 1 );
  • $hook_name (必要):鉤點的名稱。
  • $function (必要):被觸發時要執行的函式名稱。
  • $priority (選擇性):執行函式的優先級。預設值為 10。數字越小,優先級越高,越早執行。
  • $accepted_args (選擇性):傳遞給函式的參數數量。預設值為 1。

在設計的時候,優先級要謹慎設定,如果你的程式為了想要在這個鉤點最晚執行,而設定一個很大的數字,例如 999,改天又想要寫另外一個函式掛在這個節點,又想要最晚執行,而設為 1000,如此下來,都在比優先級數字誰大,這樣不是一個好現象。

接下來,舉一個例子來看看如何使用這個函式。

你想要在文章頁面的頭部區塊放進一塊 Google AdSense 的廣告區塊,可以利用 get_header 這個鉤點,如下:

function add_adsense_after_header() {
	  // 檢查是否是單篇文章頁面
    if ( is_single() ) { 
        echo '<div class="adsense-code"><!-- AdSense --></div>';
    }
}
add_action( 'get_header', 'add_adsense_after_header' );

這個例子我們定義了一個名為 add_adsense_after_header 的函式,並把它註冊到 get_header 的鉤點中。在函式內,判斷是單篇文章頁面才顯示廣告的區塊,以避免其它類型頁面受到影響。

add_filter

add_filter( $filter_name, $function, $priority = 10, $accepted_args = 1 );
  • $filter_name (必要):要掛鉤的過濾器名稱。
  • $function (必要):當過濾器被執行時,要執行的函式名稱。
  • $priority (選擇性):執行函式的優先級。設預值為 10。數字越小,優先級越高,越早執行。
  • $accepted_args (選擇性):傳遞給函式的參數數量。設預值為1。

讀者們可能有發現,這篇文章還沒有提到過濾器是什麼。它也是一種鉤點,只是這個鉤點的作用是用來把輸入的值,經過一些程式邏輯的判斷,而決定返回的值是原本的值,還是經過判斷後,被修改過的值,因此它被稱為過濾器。

我們舉一個和剛剛用在鉤點的範例相同的情境,

只不過這次是換成,要把 Google AdSense 的廣告區塊輸出到文章之前。剛剛的範例是放到頁面頭部,這次我們用 the_content 這個過濾器來做。

看一下 WordPress 核心程式碼,這個過濾器被放在什麼地方。

post-template.php 檔案內容片段
圖:post-template.php 檔案內容片段

它是被放在 the_content 這個函式中,接受的參數是從資料庫撈出來的文章內容。the_content 函式被用在佈景主題中輸出文章內容。

程式碼的第 256 行,使用 apply_filters 定義的 the_content 過濾器。這邊的函式名稱碰巧和過濾器名稱同名,別搞混了唷。

範例:

function insert_adsense_to_content( $content ) {
    // 檢查是否是單一文章頁面
    if ( is_single() ) {
        // 你的 Google AdSense 代碼
        $adsense_code = '<div class="adsense-code"><!--  AdSense  --></div>';
        // 將 AdSense 代碼插入到內容的開頭
        $content = $adsense_code . $content;
    }
    return $content;
}
add_filter( 'the_content', 'insert_adsense_to_content' );

註冊到過濾器的函式第一個參數是原本輸入的值。然後,我們把 Google 的廣告程式碼放到 $content 字串之前,結合成一個字串,並輸出。這樣一來,文章內容的前面就多了廣告區塊的顯示囉。

使用類別註冊鉤點

使用類別 (class) 註冊而非函式名稱,則鉤點和過濾器的第一個參數,為一個陣列,陣列的第一個值為類別的實例,第二個值為字串,該類別要註冊進這個鉤點的方法 (method)。

範例:

class MyCustomClass {
    public function __construct() {
        // 註冊 'custom_action_hook' 操作
        add_action( 'custom_action_hook', array( $this, 'my_custom_method' ) );
    }

    public function my_custom_method() {
        // 你的方法的具體內容
        echo '請記得訂閱加分享我的鐵人賽文章!!';
    }
}

但是在此用類別來進行註冊鉤點時要注意,最好是採用單例模式,或者要確保它只會被實例化一次,不然的話,會重覆註冊並執行,特別要小心!

過濾器的列表

整個 WordPress 程式碼中定義的過濾器實在太多了,至少數千個。幾乎每個函式都會有定義一個過濾器來提供拓展的功能,因此這方面的資料只能上官方網站去查詢:

文件查詢:https://developer.wordpress.org/reference/

筆者本身的習慣會去搜尋 WordPress 的程式碼中相對應的功能裡,有提供什麼樣的鉤點或過濾器。VsCode 也有擴充套件提供鉤點和過濾器的提示唷,有需要的話可以自行安裝。

總結

有沒有發現,鉤點和過濾器功能,可以掛載要自己要客製化的程式來改變原本的功能、增加新的功能,並修改原本的值,因此讓功能擴充變得簡單,但也容易有意想不到的臭蟲。有優點也就有缺點,挑戰程式開發者的細心和耐心。


課後思考:

為什麼外掛裝越多,網站會越慢,你覺得這和鉤點及過濾器機制有什麼樣的關聯?

前篇解答參考:

其實子佈景主題的 functions.php 不適合用來開發 WordPress 程式,請查閱本篇文章提供的 Hook 生命週期表,佈景主題會被呼叫的時機已經是 after_setup_theme,可以控制的功能順序太晚。設計一個外掛,我們的程式被載入時的鉤點在 plugins_loaded,對網站有更高的控制權,因此客製化的工作,在外掛裡做會是更合適的選擇。


上一篇
Day 5 - 使用 WordPress 子佈景主題建立客製化外觀
下一篇
Day 7 - 詳解 WordPress 的資料庫架構
系列文
從 0 到 100:WordPress 開發者的實戰手冊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言